# FIXME: PBL-17362 Script should be build type (FW or SDK) agnostic (waf & PEP8 compliant)
import bitmapgen
import png2pblpng
import os
import re
import sh
import sys
import waflib
from waftools.pebble_test import clar

def remove_old_coverage_files(bld):
    # Remove old .gcda files:
    old_coverage_files = bld.path.get_bld().ant_glob('**/*.gcda lcov.info', remove=False)
    for old_file in old_coverage_files:
        os.remove(old_file.abspath())

def update_lcov(bld):
    """ Update lcov-related files based on the results of `./waf test`"""
    print("Generating code coverage information using lcov...")
    lcov_version_cmd = ['lcov', '--version']
    # Send stdout of checking for lcov to /dev/null to hide it; stderr will still be visible
    with open(os.devnull, 'w') as devnull_fp:
        ret = bld.exec_command(lcov_version_cmd, stdout=devnull_fp)
    if ret != 0:
        bld.fatal("Error running `lcov`. Is it installed?")
    tests_path = bld.path.get_bld().abspath()
    lcov_info_out_file = os.path.join(tests_path, 'lcov.info')
    try:
        platform_specific_lcov_args = []
        if sys.platform.startswith('linux'):
            platform_specific_lcov_args.extend(['--gcov-tool', 'llvm-cov'])
        cmd = ['lcov', '--capture', '--directory', tests_path, '--output-file', lcov_info_out_file]
        cmd += platform_specific_lcov_args
        bld.cmd_and_log(cmd, quiet=waflib.Context.BOTH)
        # remove unit-tests directory itself from lcov report
        cmd = ['lcov', '--remove', lcov_info_out_file, 'tests/**', '-o', lcov_info_out_file]
        cmd += platform_specific_lcov_args
        bld.cmd_and_log(cmd, quiet=waflib.Context.BOTH)
    except waflib.Errors.WafError as e:
        print(e.stdout, '\n', e.stderr)
        bld.fatal("Error running `lcov`")
    if bld.options.coverage:
        lcov_html_directory = os.path.join(tests_path, 'lcov-html')
        genhtml_cmd = ['genhtml', lcov_info_out_file, '--output-directory', lcov_html_directory]
        try:
            bld.cmd_and_log(genhtml_cmd, quiet=waflib.Context.BOTH)
        except waflib.Errors.WafError as e:
            print(e.stdout, '\n', e.stderr)
            bld.fatal("Error running `genhtml`")
        index_html = os.path.join(lcov_html_directory, 'index.html')
        print("Updated coverage report at %s" % index_html)

def convert_png_to_pbi(task):
    src_png = task.inputs[0].srcpath()
    dest_pbi = task.outputs[0].srcpath()
    bitdepth = None

    if any(word in dest_pbi for word in ['.8bit.', '~snowy', '~spalding', '~cutts', '~robert']):
        img_fmt = 'color_raw'
    elif any(word in dest_pbi for word in ['.1bit.', '~tintin']):
        img_fmt = 'bw'
    else:
        img_fmt = 'color'  # raw and palettized color images
        bit_suffix = re.search(r'(\d)bitpalette\.png', dest_pbi)
        if bit_suffix:
            bitdepth = int(bit_suffix.group(1))

    pb = bitmapgen.PebbleBitmap(src_png, bitmap_format=img_fmt, crop=False, bitdepth=bitdepth)
    pb.convert_to_pbi_file(dest_pbi)

def convert_png_to_pblpng(task):
    src_png = task.inputs[0].srcpath()
    dest_png = task.outputs[0].srcpath()

    # we need to be able to skip the png generator for specific test pngs flagged 'raw'
    # and copy over the original file
    if dest_png.endswith('.raw.png'):
        task.exec_command('cp -f {0} {1}'.format(task.inputs[0].abspath(), task.outputs[0].abspath()))
    else:
        palette_name = 'pebble64'
        bitdepth = None
        bit_suffix = re.search(r'(\d)bit(palette)?\.png', dest_png)

        if bit_suffix:
            bitdepth = int(bit_suffix.group(1))
        elif any(word in dest_png for word in ['~snowy', '~spalding', '~cutts', '~robert']):
            bitdepth = 8
        elif any(word in dest_png for word in ['~tintin']):
            bitdepth = 1
            palette_name = 'pebble2'

        png2pblpng.convert_png_to_pebble_png(src_png, dest_png,
                                             palette_name=palette_name, bitdepth=bitdepth)

# Creates a job for each PNG in the test_images directory.
# Each of these PNGs will be converted into a PBI in the build directory.
# Also exports TEST_IMAGES_PATH to point to the location of the PBIs.
def generate_test_pbis(ctx):

    test_image_pbis = []

    bitmapgen_path = ctx.path.find_node('../tools/bitmapgen.py').abspath()

    for png_file in ctx.path.find_node('test_images').ant_glob("*.png"):
        dest_pbi = png_file.get_bld().change_ext('.pbi')

        # if the image contains Xbit in the name, then generate both 1bit and 8bit PBI images
        if ".Xbit." in str(dest_pbi):
            dest_pbi = png_file.get_bld().change_ext('.1bit.pbi', '.Xbit.png')
            ctx(name='png_to_pbi', rule=convert_png_to_pbi, source=png_file, target=dest_pbi,
                bmp_script=bitmapgen_path)
            test_image_pbis.append(dest_pbi)

            dest_pbi = png_file.get_bld().change_ext('.8bit.pbi', '.Xbit.png')
            ctx(name='png_to_pbi', rule=convert_png_to_pbi, source=png_file, target=dest_pbi,
                bmp_script=bitmapgen_path)
            test_image_pbis.append(dest_pbi)
        else:
            ctx(name='png_to_pbi', rule=convert_png_to_pbi, source=png_file, target=dest_pbi,
                bmp_script=bitmapgen_path)
            test_image_pbis.append(dest_pbi)

    return test_image_pbis

# Creates a job for select PNG in the test_images directory.
# Each of these PNGs will be converted into a Pebble PNG8 in the build directory.
def generate_test_pngs(ctx):

    test_image_pngs = []

    pblpng_resources_list = []
    pblpng_resources_list.extend(
        ctx.path.find_node('test_images').ant_glob("test_png__*.png"))

    for png_file in pblpng_resources_list:
        dest_png = png_file.get_bld()

        ctx(name='png_to_pblpng', rule=convert_png_to_pblpng, source=png_file, target=dest_png)
        test_image_pngs.append(dest_png)

    return test_image_pngs

def copy_test_pngs_to_build_dir(ctx):
    test_image_pngs = []

    # copy over test specific files such as png, apng
    copy_resources_list = []
    copy_resources_list.extend(
        ctx.path.find_node('test_images').ant_glob("test_bitblt_circular__*.png"))
    copy_resources_list.extend(
        ctx.path.find_node('test_images').ant_glob("test_gbitmap_sequence__*.apng"))
    copy_resources_list.extend(
        ctx.path.find_node('test_images').ant_glob("test_kino_reel__*.apng"))
    copy_resources_list.extend(
        ctx.path.find_node('test_images').ant_glob("test_graphics_draw_text_flow__*.png"))
    for copy_file in copy_resources_list:
        dest_file = copy_file.get_bld()
        ctx(name='copy_png', rule='cp -f ${SRC} ${TGT}', source=copy_file, target=dest_file)
        test_image_pngs.append(dest_file)

    return test_image_pngs


def copy_pdc_files_to_build_dir(ctx):
    test_image_pdc_files = []
    copy_resources_list = ctx.path.find_node('test_images').ant_glob("*.pdc")
    for copy_file in copy_resources_list:
        dest_file = copy_file.get_bld()
        ctx(name='copy_pdc', rule='cp -f ${SRC} ${TGT}', source=copy_file, target=dest_file)
        test_image_pdc_files.append(dest_file)

    return test_image_pdc_files


def copy_pfo_files_to_build_dir(ctx):
    test_image_pfo_files = []
    copy_resources_list = ctx.path.find_node('test_images').ant_glob("*.pfo")
    for copy_file in copy_resources_list:
        dest_file = copy_file.get_bld()
        ctx(name='copy_pfo', rule='cp -f ${SRC} ${TGT}', source=copy_file, target=dest_file)
        test_image_pfo_files.append(dest_file)

    return test_image_pfo_files


def convert_test_pdcs(ctx):
    test_image_pdc_files = []

    resources_list = ctx.path.find_node('test_images').ant_glob("*.svg")
    resources_list.extend(ctx.path.find_node('test_images').ant_glob("*", src=False, dir=True))
    import sys
    sys.path.insert(0, ctx.path.parent.abspath())

    from tools.generate_pdcs import pdc_gen
    def convert_svg_image(task):
        pdc_gen.create_pdc_from_path(
                    task.inputs[0].abspath(),
                    task.outputs[0].abspath(),
                    viewbox_size=(0, 0),
                    verbose=False,
                    duration=0,
                    play_count=0)

    def convert_svg_sequence(task):
        dir_name = os.path.dirname(task.inputs[0].abspath())
        pdc_gen.create_pdc_from_path(
                    dir_name,
                    task.outputs[0].abspath(),
                    viewbox_size=(0, 0),
                    verbose=False,
                    duration=33,
                    play_count=1)

    for input_node in resources_list:
        output_pdc = input_node.get_bld().change_ext('.pdc')

        test_image_pdc_files.append(output_pdc)
        if os.path.isdir(input_node.abspath()):
            conversion_rule = convert_svg_sequence
            source_files = input_node.ant_glob("*.svg")
        else:
            conversion_rule = convert_svg_image
            source_files = [input_node]

        ctx(rule=conversion_rule,
            source=source_files,
            target=output_pdc)

    return test_image_pdc_files


def convert_pdc_to_pbi(ctx):
    bitmapgen_path = ctx.path.find_node('../tools/bitmapgen.py').abspath()

    test_pdc_pbis = []

    pdc_files = ctx.path.find_node('test_images').ant_glob("test_pdc__*.pdc")
    pdc2png = ctx.path.get_bld().parent.make_node('pdc2png')

    for pdc in pdc_files:
        dest_pdc = pdc.get_bld().change_ext('.pdc.pdc')
        src_png = dest_pdc.change_ext('.png')
        dest_pbi = dest_pdc.change_ext('.pbi')
        ctx(rule='cp ${SRC} ${TGT}', source=pdc, target=dest_pdc)
        ctx(rule='${SRC[0].abspath()} ${SRC[1].abspath()}', source=[pdc2png, dest_pdc], target=src_png)
        ctx(rule=convert_png_to_pbi, source=src_png, target=dest_pbi, bmp_script=bitmapgen_path)
        test_pdc_pbis.append(dest_pbi)

    return test_pdc_pbis

def options(opt):
    gr = opt.add_option_group('test options')
    gr.add_option('-D', '--debug_test', action='store_true',
        help='Execute tests within GDB. Use alongside -M.')
    gr.add_option('-M', '--match', dest='regex', default=None, action='store',
        help='Run regex match tests. Example: ./waf test -M "test.*resource.*"')
    gr.add_option('-L', '--list_tests', dest='list_tests', action='store_true',
        help='List all test names. Usually used in conjunction with -M. Example: '
             './waf test -M test_animation -L')
    gr.add_option('-T', '--test_name', dest='test_name', default=None, action='store',
        help='Run only the given test name. Usually used in conjunction with -M. Example: '
             './waf test -M test_animation -T unschedule')
    gr.add_option('-C', '--coverage', dest='coverage', action='store_true', help='Generate gcov test coverage data and use lcov to generate HTML report')
    gr.add_option('--show_output', action='store_true', help='show test output')
    gr.add_option('--no_run', action='store_true', help='Do not run the tests, just build them')
    gr.add_option('--no_images', action='store_true', help='skip generation of test images, '
                  'which are only required for some tests and can slow down build times')

def build(bld):
    if bld.options.debug_test:
        if not bld.options.regex:
            bld.fatal('When using --debug_test, you must also use --match to'
                      ' specify the test file to debug')
        bld.env.append_value('DEFINES', 'UNITTEST_DEBUG')

    bld.env.CFLAGS.append('-I' + bld.path.abspath() + '/../src/fw/util/time')
    bld.env.CFLAGS.append('-I' + bld.path.abspath() + '/../src/include')

    # clang on Linux errors on true == true or false == false compile-time assertions
    bld.env.CFLAGS.append('-Wno-tautological-compare')

    # Any test in this list won't be compiled
    bld.env.BROKEN_TESTS = [
        'test_app_fetch_endpoint.c',
        'test_graphics_draw_text_flow.c',
        'test_perimeter.c',
        'test_ancs_pebble_actions.c',
        'test_timeline_actions.c',
        'test_bluetooth_persistent_storage_prf.c',
        'test_bluetooth_persistent_storage.c',
        'test_session.c',
        'test_session_receive_router.c',
        'test_compositor.c',
        'test_floor.c',
        'test_pow.c',
        'test_ams.c',
        'test_ams_util.c',
        'test_gap_le_advert.c',
        'test_bt_conn_mgr.c',
        'test_gatt_client_accessors.c',
        'test_gatt_client_discovery.c',
        'test_gatt_client_subscriptions.c',
        'test_gatt_service_changed_client.c',
        'test_gatt_service_changed_server.c',
        'test_gap_le_connect.c',
        'test_ancs_util.c',
        'test_ancs.c',
        'test_kernel_le_client.c',
        'test_ppogatt.c',
        'test_graphics_circle.c'
    ]

    # Don't run the python tool tests because they exercise a lot of old python2 code that still needs to be updated
    bld.env.PYTHON_TOOL_TESTS_DISABLED = True

    # Disable warning promotion. Not ideal, but gets most of the tests running again without straight up disabling the new warnings
    bld.env.CFLAGS.append('-Wno-error')

    # Many tests operate on a set of test images and require tools to process these
    # images and therefore need extra defines. Set up our environment first before running any
    # tests.
    test_images_dest_dir = bld.path.find_node('test_images').get_bld()

    # Set up the fail directory, and make it. This is used to output data from the tests for
    # comparison with the expected results.
    fail_dir = test_images_dest_dir.parent.make_node('failed')
    fail_path = fail_dir.abspath().strip()
    sh.rm('-rf', fail_path)
    fail_dir.mkdir()

    def convert_to_emscripten_fs_path_if_needed(node):
        real_fs_abspath = node.abspath()
        if bld.variant != 'test_rocky_emx':
            return real_fs_abspath
        # When transpiling unittests with Emscripten, the host machine's
        # filesystem is mounted at /node_fs, so we need to translate paths.
        return '/node_fs' + real_fs_abspath

    bld.env.test_image_defines = [
        'TEST_IMAGES_PATH="%s"' % convert_to_emscripten_fs_path_if_needed(test_images_dest_dir),
        'TEST_OUTPUT_PATH="%s"' % convert_to_emscripten_fs_path_if_needed(fail_dir),
        'PBI2PNG_EXE="%s"' % bld.path.find_node('../tools/pbi2png.py').abspath()]

    # Add test_pbis or test_pngs to runtime_deps for tests that require them
    if not bld.options.no_images:
        bld.env.test_pbis = generate_test_pbis(bld)
        bld.env.test_pngs = copy_test_pngs_to_build_dir(bld)
        bld.env.test_pngs.extend(generate_test_pngs(bld))
        bld.env.test_pfos = copy_pfo_files_to_build_dir(bld)
        # Includes reference pdc and pbi generated from ref png
        bld.env.test_pdcs = bld.env.test_pbis + convert_test_pdcs(bld) + copy_pdc_files_to_build_dir(bld)
        bld.env.pdcs2png_test_files = bld.env.test_pbis + convert_pdc_to_pbi(bld)

    if bld.options.coverage:
        bld.env.append_value('CFLAGS', '-fprofile-arcs')
        bld.env.append_value('CFLAGS', '-ftest-coverage')
        bld.env.append_value('LINKFLAGS', '--coverage')
    test_wscript_dirs = [os.path.dirname(f.abspath()) for f in bld.path.ant_glob('**/wscript')]
    for dir in test_wscript_dirs:
        bld.recurse(dir)
    if bld.options.coverage:
        bld.add_pre_fun(remove_old_coverage_files)
        bld.add_post_fun(update_lcov)


# vim:filetype=python
